package com.api.util.repo;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class DtoProperties implements DtoRepository {

	private final PropertiesAppender properties;

	public DtoProperties(String propertiesFile) {
		this.properties = new PropertiesAppender(propertiesFile);
	}

	@Override
	public boolean existsDto(String name, Class<?> dtoClass) {
		String classNamePrefix = dtoClass.getSimpleName() + "." + name;
		Map<String, String> props = properties.getProperties(classNamePrefix);
		return !props.isEmpty();
	}

	@Override
	public <T> T getDto(String name, Class<T> dtoClass) {
		return getDto("", name, dtoClass);
	}

	private <T> T getDto(String prefix, String name, Class<T> dtoClass) {
		Object value = null;

		Class<?> dtoBoxedClass = getBoxedType(dtoClass);
		String propertyName = getPropertyName(prefix, name, dtoBoxedClass);

		if (isSimpleType(dtoClass)) {
			String propertyValue = properties.getProperty(propertyName);
			value = getValueForType(dtoBoxedClass, propertyValue);
		} else {
			value = getObjectDto(propertyName, dtoClass);
		}
		@SuppressWarnings("unchecked")
		T dtoInstance = (T) value;
		return dtoInstance;
	}

	public <T> T getObjectDto(String prefix, Class<T> dtoClass) {
		try {
			T dtoInstance = dtoClass.newInstance();
			Field[] fields = dtoClass.getDeclaredFields();
			for (Field field : fields) {
				boolean accessible = field.isAccessible();
				field.setAccessible(true);

				Class<?> typeClass = field.getType();
				Object value = getDto(prefix, field.getName(), typeClass);

				field.set(dtoInstance, value);
				field.setAccessible(accessible);

			}
			return dtoInstance;
		} catch (Exception e) {
			throw new IllegalStateException(String.format("Couldn't get dto instance for class '%s'", dtoClass), e);
		}
	}

	private Object getValueForType(Class<?> type, String value) {
		Object obj = value;
		if (type.equals(Long.class)) {
			obj = Long.valueOf(value);
		} else if (type.equals(Integer.class)) {
			obj = Integer.valueOf(value);
		}
		return obj;
	}

	@Override
	public void saveDto(String name, Object dto) {
		Map<String, String> props = getDtoProperties("", name, dto);
		properties.appendProperties(props);
	}

	private Map<String, String> getDtoProperties(String prefix, String name, Object dto) {
		Map<String, String> props = new HashMap<String, String>();
		Class<?> dtoBoxedClass = getBoxedType(dto.getClass());

		String propertyName = getPropertyName(prefix, name, dtoBoxedClass);
		if (isSimpleType(dto.getClass())) {
			String value = String.valueOf(dto);
			props.put(propertyName, value);
		} else {
			Map<String, String> map = getObjectDtoProperties(propertyName, dto);
			props.putAll(map);
		}
		return props;
	}

	private Map<String, String> getObjectDtoProperties(String prefix, Object dto) {
		Map<String, String> props = new HashMap<String, String>();
		try {
			Class<?> dtoClass = dto.getClass();
			Field[] fields = dtoClass.getDeclaredFields();
			for (Field field : fields) {
				boolean accessible = field.isAccessible();
				field.setAccessible(true);
				Object value = field.get(dto);
				field.setAccessible(accessible);

				if (value != null) {
					Map<String, String> map = getDtoProperties(prefix, field.getName(), value);
					props.putAll(map);
				}
			}
		} catch (Exception e) {
			throw new IllegalStateException(String.format("Couln't save dto instance '%s'", dto), e);
		}
		return props;
	}

	private String getPropertyName(String prefix, String name, Class<?> aClass) {
		String propertyPrefix = prefix;
		if (!prefix.equals("")) {
			propertyPrefix += ".";
		}
		return propertyPrefix + aClass.getSimpleName() + "." + name;
	}

	private boolean isSimpleType(Class<?> aClass) {
		Class<?>[] simpleClasses = { String.class, Integer.class, Long.class, Boolean.class };
		boolean simpleType = aClass.isPrimitive();
		int i = 0;
		while (i < simpleClasses.length && !simpleType) {
			Class<?> simpleClass = simpleClasses[i];
			simpleType = simpleClass.isAssignableFrom(aClass);
			i++;
		}
		return simpleType;
	}

	private Class<?> getBoxedType(Class<?> aClass) {
		Class<?> boxedClass = null;
		if (aClass.isPrimitive()) {
			if (aClass.getSimpleName().equals("long")) {
				boxedClass = Long.class;
			} else if (aClass.getSimpleName().equals("int")) {
				boxedClass = Integer.class;
			}
		} else {
			boxedClass = aClass;
		}

		if (boxedClass == null) {
			throw new IllegalStateException(String.format("Class '%s' not recognized for boxing", aClass.getSimpleName()));
		}
		return boxedClass;
	}
}
